#ifndef TRAININGCONTAINER_H
#define TRAININGCONTAINER_H

#include <string>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <utility>
#include <ctime>

#include "sdm/training.h"

#ifndef rate_t
#define rate_t float
#endif

class TrainingContainer {

public:

    enum Exception {
        FileNotFound
    };

    enum Clazz {
        Positive,
        Negative
    };

protected:
    struct training_t {
        rate_t positive, negative;
        size_t hiddenLayer;
    };

    std::vector< const std::pair<data_t*, data_t*>* > positive;
    std::vector< const std::pair<data_t*, data_t*>* > negative;
    std::vector< training_t* > traning;
    std::string inputfile;

    data_t threshold;
    size_t input;
    size_t output;
    size_t size;
    size_t repeat;
    size_t iterations;

    void readData(const std::string &inputfile);
    TrainingContainer::Clazz classify(const data_t* output);
    void build(const float positive_rate, const float negative_rate, std::vector<const input_t*> &traning, std::vector<const input_t*> &test);

public:
    TrainingContainer(const std::string &configfile);

    void run();
};

#endif // TRAININGCONTAINER_H


void TrainingContainer::readData(const std::string &filename) {
    std::ifstream stream(filename.c_str());

    if ( !stream.is_open() ) {
        throw TrainingContainer::FileNotFound;
    }

    stream >> this->size;
    stream >> this->input;
    stream >> this->output;

    data_t *input;
    data_t *output;

    for ( size_t i = 0; i < this->size; i++ ) {
        input = new data_t[this->input];
        output = new data_t[this->output];

        for ( size_t j = 0; j < this->input; j++ )
            stream >> input[j];
        for ( size_t j = 0; j < this->output; j++ )
            stream >> output[j];

        if ( this->classify(output) == TrainingContainer::Positive ) {
            positive.push_back(new std::pair<data_t*, data_t*>(input, output));
        } else {
            negative.push_back(new std::pair<data_t*, data_t*>(input, output));
        }
    }
}

TrainingContainer::Clazz TrainingContainer::classify(const double *output){
    return output[0] > this->threshold ? TrainingContainer::Positive : TrainingContainer::Negative;
}

void TrainingContainer::build(const float positive_rate, const float negative_rate, std::vector<const std::pair<double*, double*>*>& train, std::vector<const std::pair<double*, double*>*>& test){

    size_t size_p = this->positive.size() * positive_rate,
            size_n = this->negative.size() * negative_rate;

    std::random_shuffle(this->positive.begin(), this->positive.end());
    std::random_shuffle(this->negative.begin(), this->negative.end());

    train.resize(size_p+size_n);
    test.resize( (positive.size()-size_p) + (negative.size()-size_n) );
    std::copy(this->positive.begin(), this->positive.begin() + size_p, train.begin());
    std::copy(this->negative.begin(), this->negative.begin() + size_n, train.begin() + size_p);
    std::copy(this->positive.begin() + size_p, this->positive.end(), test.begin() );
    std::copy(this->negative.begin() + size_n, this->negative.end(), test.begin() + (positive.size()-size_p) );
}

TrainingContainer::TrainingContainer(const std::string& configfile): threshold(.5) {
    std::srand ( unsigned ( std::time(0) ) );
    std::ifstream stream(configfile.c_str());
    size_t size;

    if ( !stream.is_open() ) {
        throw TrainingContainer::FileNotFound;
    }

    std::getline(stream, inputfile);

    stream >> size;
    stream >> this->repeat;
    stream >> this->iterations;

    this->readData(inputfile);
    this->traning.resize(size);

    for ( size_t i = 0; i < size; i++ ) {
        training_t *data = new training_t;

        stream >> data->positive;
        stream >> data->negative;
        stream >> data->hiddenLayer;

        this->traning[i] = data;
    }
}

void TrainingContainer::run() {

    std::vector<training_t*>::iterator iterator = this->traning.begin(),
            end = this->traning.end();

    size_t index = 0;

    std::cout << std::endl;
    std::cout << "\tTeste\tRep\tStatus\n";
    while ( iterator < end ) {
        training_t *data = *iterator;
        std::vector<size_t> layers({
                                       this->input,
                                       data->hiddenLayer,
                                       this->output
                                   });

        Training * t = new Training(layers);
        t->setMaxIterations(this->iterations);
        std::ofstream stream;
        std::string filename = "output_";
        std::vector < const input_t* > train;
        std::vector < const input_t* > test;

        filename += std::to_string(index);
        stream.open(filename, std::ios::out);

        stream << "Arquivo de origem: " << this->inputfile << std::endl;
        stream << "Número de neroneos: " << data->hiddenLayer << std::endl;
        stream << "Quantidade de positivos: " << data->positive << std::endl;
        stream << "Quantidade de negativos: " << data->negative << std::endl;

        for ( size_t i = 0; i < this->repeat; i++ ) {
            t->randomize();
            this->build(data->positive, data->negative, train, test);

            stream << "\n-----------------------------------------------------\n";

            std::time_t begin = std::time(nullptr);
            std::cout << "\t" << index << "\t" << i << "\tTreinando...";
            t->train( train );
            std::cout << "\r\t" << index << "\t" << i << "\tAnalizando.\t";
            t->test( train, stream );
            stream << std::endl;
            t->test( test, stream );
            std::time_t end = std::time(nullptr);

            std::cout << "\r\t" << index << "\t" << i << "\tFinalizado em " << end-begin << "s" << std::endl;
        }



        stream.close();

        index++;
        iterator++;
    }
}
